package cn.dmoncat.util.influx;

import cn.demoncat.util.exception.ReflexRuntimeException;
import cn.demoncat.util.lang.LocalDateUtil;
import com.influxdb.annotations.Column;
import com.influxdb.annotations.Measurement;
import com.influxdb.client.domain.WritePrecision;
import com.influxdb.client.write.Point;
import lombok.Data;

import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Measurement 映射
 *
 * @author 延晓磊
 * @since 2024年07月02日
 */
@Data
public class MeasurementMapper {

    /**
     * measurement
     *
     * 类注解 @Measurement(name = "name")
     */
    private String name;
    /**
     * timestamp
     *
     * 字段注解  @Column(timestamp=true)
     * 字段类型  Long；纳秒；非空
     * 注意：Java获取不到纳秒，所以主动赋值时传毫秒，将后缀6位随机数；如果是自动赋值，则由Go获取纳秒，可以 /1000000 转毫秒
     */
    private Field timestamp;
    /**
     * tag
     *
     * 字段注释  @Column(tag = true)
     * 字段类型  string；非空
     */
    private List<Field> tags = new ArrayList<>();
    /**
     * field
     *
     * 字段注解  @Column
     * 字段类型  String,Integer,Long,Double,Boolean；非空
     */
    private List<Field> fields = new ArrayList<>();
    /**
     * field 类型映射
     */
    private Map<String,String> fieldTypes = new HashMap<>();

    /**
     * 映射集
     */
    private static final Map<String,MeasurementMapper> mappers = new HashMap<>();


    /**
     * 获取Measurement映射
     *
     * @param clazz
     * @return
     */
    public static MeasurementMapper get(Class<?> clazz){
        MeasurementMapper mapper = mappers.get(clazz.getName());
        if (mapper == null) {
            mapper =new MeasurementMapper();
            // 表
            Measurement measurement = clazz.getAnnotation(Measurement.class);
            if (measurement == null) {
                throw new ReflexRuntimeException("类型 " + clazz + " 未注解 @Measurement");
            }
            mapper.setName(measurement.name());
            // 列
            Field[] fields = clazz.getDeclaredFields();
            Column column;
            for (Field field : fields) {
                column = field.getAnnotation(Column.class);
                if (column != null) {
                    // 授权
                    if (!field.isAccessible()) {
                        field.setAccessible(true);
                    }
                    // 分类
                    if (column.tag()) {
                        // 索引
                        mapper.getTags().add(field);
                    }else if (column.timestamp()) {
                        // 时间戳
                        mapper.setTimestamp(field);
                    }else {
                        // 数据
                        mapper.getFields().add(field);
                        mapper.getFieldTypes().put(field.getName(), field.getType().getSimpleName());
                    }
                }
            }
            mappers.put(clazz.getName(), mapper);
        }
        return mapper;
    }

    /**
     * 转换为Point
     *
     * @param o     行数据
     * @return
     */
    private Point convertPoint(Object o) throws IllegalAccessException {
        // 转换
        Point point = new Point(this.getName());
        // timestamp
        if (this.getTimestamp() != null) {
            point.time(LocalDateUtil.toNs(this.getTimestamp().getLong(o)), WritePrecision.MS);
        }
        // tag
        for (Field tag : this.getTags()) {
            point.addTag(tag.getName(), tag.get(o).toString());
        }
        // field
        for (Field field : this.getFields()) {
            switch (this.getFieldTypes().get(field.getName())) {
                case "String":
                    point.addField(field.getName(), field.get(o).toString());
                    break;
                case "Integer":
                    point.addField(field.getName(), field.getInt(o));
                    break;
                case "Long":
                    point.addField(field.getName(), field.getLong(o));
                    break;
                case "Double":
                    point.addField(field.getName(), field.getDouble(o));
                    break;
                case "Boolean":
                    point.addField(field.getName(), field.getBoolean(o));
                    break;
                default:
                    break;
            }
        }
        return point;
    }

    /**
     * 获取Point
     *
     * @param o     行数据
     * @return
     */
    public Point toPoint(Object o) {
         try {
            return convertPoint(o);
         }catch (Exception e){
             throw new ReflexRuntimeException("类型 " + o.getClass() + " 转换 Point 失败", e);
         }
    }

    /**
     * 获取Point
     *
     * @param os     行数据集
     * @return
     */
    public List<Point> toPoints(Collection<?> os) {
        return os.stream().map(this::toPoint).collect(Collectors.toList());
    }
}
